#pragma once

#include <queue>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <iostream>
#include <thread>

namespace eniac
{

template<typename T>
class r_queue_s
{
private:
    mutable std::mutex m_mutex;
    std::queue<T> m_queue;
    std::condition_variable m_condition;
    bool m_shutdown = false;

public:
    r_queue_s() = default;

    r_queue_s(const r_queue_s&) = delete;
    r_queue_s(r_queue_s&&) = delete;
    r_queue_s& operator=(const r_queue_s&) = delete;
    r_queue_s& operator=(r_queue_s&&) = delete;

    void push(T& v)
    {
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            m_queue.push(std::move(v));
        }
        m_condition.notify_one();
    }

    bool try_pop(T& v)
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (m_queue.empty() || m_shutdown)
        {
            return false;
        }
        v = std::move(m_queue.front());
        m_queue.pop();
        return true;
    }

    bool wait_and_pop(T& v)
    {
        std::unique_lock<std::mutex> lock(m_mutex);

        m_condition.wait(lock, [this]()
        {
            return !m_queue.empty() || m_shutdown; 
        });
        
        if (true == m_shutdown)
        {
            return false;
        }
        
        v = std::move(m_queue.front());
        m_queue.pop();
        return true;
    }
    
    bool empty() const
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        return m_queue.empty();
    }
    
    size_t size() const
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        return m_queue.size();
    }
    
    void shutdown()
    {
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            m_shutdown = true;
        }
        m_condition.notify_all();
    }

    bool is_shutdown() const
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        return m_shutdown;
    }
};

}